/**
    ATClab - scriptable Air Traffic Control simulations.
    Copyright (C) 2010 openatclab@gmail.com

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
**/

#include "taskfactory.h"

#include "display_task.h"
#include "feedback_task.h"
#include "inputtask.h"
#include "instructiontask.h"
#include "trialtask.h"

#include "nodehandler.h"
#include "document.h"			// "xml_document.h"
#include "presenter.h"
#include "ParallelPortIO.h"
#include "atc_types.h"

#ifdef _WIN32
	#include <assert.h>
#endif

using namespace pact;
using namespace plugin;


////////////////////////////////////////////////////////////////////////////////////////////////////
//
//
//
//--------------------------------------------------------------------------------------------------
// [public] interface
//

/**
 * Factory Method for creating presentaiton task widgets.
 * The caller is responsible for heap object.
 *
 * @param doc The Document.
 * @param node Document Node to process.
 * @return A Task widget.
 * @throws taskfactory_error if node is not a valid type.
 *
 * pre-condition: doc->isValid()
 * pre-condition: node->isValid()
 */
Task* TaskFactory::createTask(
	const xml::Document* doc,
	const xml::DocumentNode* node
){
	assert( doc );
	assert( node );

	if ( "input" == node->type ) {
		return TaskFactory::createInputTask( doc, node );
	}
	else if ( "display" == node->type ) {
		return TaskFactory::createDisplayTask( doc, node );
	}
	else if (node->type == "scoreFeedback")
	{
		return TaskFactory::createFeedbackTask(doc, node);
	}
	else if ( "instruction" == node->type ) {
		return TaskFactory::createInstructionTask( doc, node );
	}
	else if ( "trial" == node->type ) {
		std::string trialCountRef;
		
		// Increment the global trial count
		if (doc->attributeExists(node, "trial_count_global_ref"))
		{
			trialCountRef = doc->getAttributeValue(node, "trial_count_global_ref");

			pact::GlobalVariables::iterator it = 
				pact::Presenter::instance()->globals().find(trialCountRef);

			if (it != pact::Presenter::instance()->globals().end())
			{
				int count = it->second->value();
				it->second->set_value(++count);
			}
		}
		// What type of trial is this?
		std::string trialType;

		if (doc->attributeExists(node, "type"))
		{
			trialType = doc->getAttributeValue(node, "type");
		}

		Task* ret = NULL;

		if (trialType == "practice")
		{
			ret = TaskFactory::createTrialTask(doc, node, pact::TRIAL_TASK_PRACTICE);
		}
		else
		{
			ret = TaskFactory::createTrialTask( doc, node );
		}

		return ret;
	}
	else {
		throw taskfactory_error(
			std::string( "illegal task entry <" ) + node->type + ">"
		);
	}
};


//--------------------------------------------------------------------------------------------------
// [public] Hidden constructors
//

/**
 *
 */
InputTask* TaskFactory::createInputTask(
	const xml::Document* doc,
	const xml::DocumentNode* node
){
	assert( "input" == node->type );

	InputTask *task = NULL;

	if (doc->attributeExists(node, "idxref"))
	{
		// Is this Input task a linked task that is optionally displayed based on a 
		// selection made by a user on a previous task

		if (Presenter::instance()->GlobalIdx() != "")
		{
			task = new InputTask();
			doc->process_children(doc->getReferencedNode(
									Presenter::instance()->GlobalIdx()), 
									InputNodeHandler(doc, task)
			);
					
			Presenter::instance()->SetGlobalIdx("");

		}
		else
		{
			const xml::DocumentNode* refNode = doc->getReferencedNode(doc->getAttributeValue(node, "idxref"));

			if (doc->attributeExists(refNode, "type"))
			{
				// No task to display for the previous user selection. Log results
				std::string type;
				std::string logLabel;

				if (doc->attributeExists(refNode, "type"))
				{
					type = doc->getAttributeValue(refNode, "type");
				}


				if (type == "srGoal")
				{
					logLabel = "srgoal";
						
					pact::GlobalVariables::iterator vit = 
						pact::Presenter::instance()->globals().find("SRGoal");
					
					if (vit != pact::Presenter::instance()->globals().end())
					{
						vit->second->set_value(0);
					}
				}
				else if (type == "normGoal")
				{
					logLabel = "ngoal";

					pact::GlobalVariables::iterator vit = 
						pact::Presenter::instance()->globals().find("NormGoal");
					
					if (vit != pact::Presenter::instance()->globals().end())
					{
						vit->second->set_value(0);
					}
				}
		
				if ((type != "") && (logLabel != ""))
				{
					logging::KVPairs kvs;

					kvs.push_back(std::make_pair(logLabel, std::string("0")));
					logging::Logger::instance()->info(kvs);
				}
			}
			else
			{
				task = new InputTask();
				doc->process_children(refNode, InputNodeHandler(doc, task));
			}
		}
	}

	return task;
}

/*!
 *
 */
DisplayTask* TaskFactory::createDisplayTask(
	const xml::Document* doc,
	const xml::DocumentNode* node
){
	assert( "display" == node->type );

	DisplayTaskBuilder builder;
	
	doc->process_children(
		doc->getReferencedNode(doc->getAttributeValue(node, "idxref")), 
		DisplayNodeHandler( doc, &builder )
	);
	
	return builder.build();
}

FeedbackTask*
TaskFactory::createFeedbackTask(const xml::Document* a_doc,
								const xml::DocumentNode* a_node)
{
	assert(a_node->type == "scoreFeedback");

	FeedbackTask* fbTask = NULL;
	
	logging::KVPairs kvs;
	std::string feedbackChoice = "0";
	std::string feedbackType = "0";
	std::string feedbackValue;

	if (Presenter::instance()->GlobalIdx() != "")
	{
		// The participant was given a choice to select the type of feedback required.
		// Thats why Presenter::instance()->GlobalIdx is not ""
		FeedbackTaskBuilder builder;

		// The specific feedback task to display must be first determined by an input task
		// which set the GlobalIdx variable in the Presenter.
		a_doc->process_children(a_doc->getReferencedNode(Presenter::instance()->GlobalIdx()),
								FeedbackNodeHandler(a_doc, &builder));

		Presenter::instance()->SetGlobalIdx("");

		fbTask = builder.build();

		// Records for logging purpose
		feedbackChoice = atc::to_string(FEEDBACK_PARTICIPANT_CHOICE);

		pact::GlobalVariables::iterator it = 
			pact::Presenter::instance()->globals().find(CHOICE_QUESTION_SELECTION);

		if (it != pact::Presenter::instance()->globals().end())
		{
			feedbackType = atc::to_string(it->second->value());
		}
	}
	else
	{
		// Participant was forced to receive a certain type of feedback which is specified
		// by a_node's idx ref attribute
		FeedbackTaskBuilder builder;

		const xml::DocumentNode* feedbackNode = a_doc->getReferencedNode(a_doc->getAttributeValue(a_node, "idxref"));
		a_doc->process_children(feedbackNode, FeedbackNodeHandler(a_doc, &builder));

		fbTask = builder.build();

		// Records for logging purpose		
		feedbackChoice = atc::to_string(FEEDBACK_NO_CHOICE);
		feedbackType = atc::to_string(FEEDBACK_GENERAL);
		
		if (a_doc->attributeExists(feedbackNode, "type"))
		{
			std::string fbType = a_doc->getAttributeValue(feedbackNode, "type");
			
			if (fbType == "selfReferent")
			{
				feedbackType = atc::to_string(FEEDBACK_SELF_REFERENT);
			}
			else if (fbType == "normative")
			{
				feedbackType = atc::to_string(FEEDBACK_NORMATIVE);
			}
		}
	}
	
	if ((feedbackType == atc::to_string(FEEDBACK_SELF_REFERENT)) ||
		(feedbackType == atc::to_string(FEEDBACK_NORMATIVE)))
	{
		pact::GlobalVariables::iterator it = 
			pact::Presenter::instance()->globals().find(FEEDBACK_VALUE);

		if (it != pact::Presenter::instance()->globals().end())
		{
			feedbackValue = atc::to_string(it->second->value());
		}
	}

	kvs.push_back(std::make_pair(std::string("fbchoice"), feedbackChoice));
	kvs.push_back(std::make_pair(std::string("fbtype"), feedbackType));
	kvs.push_back(std::make_pair(std::string("fbsign"), feedbackValue));
	logging::Logger::instance()->info( kvs );	

	return fbTask;
}


/**
 * Build Instruction Presentation Task.
 *
 * pre-condition: node->type == "instruction"
 */
InstructionTask* TaskFactory::createInstructionTask(
	const xml::Document* doc,
	const xml::DocumentNode* node
){
	assert("instruction" == node->type);
	InstructionTask *task = new InstructionTask();
	doc->process_children(
		doc->getReferencedNode(doc->getAttributeValue(node, "idxref")), 
		InstructionNodeHandler(doc, task)
	);
	return task;
}


/**
 * Build ATC Trial Presentation Task.
 *
 * pre-condition: node->type == "trial"
 */
TrialTask* 
TaskFactory::createTrialTask(const xml::Document* doc,	
							 const xml::DocumentNode* node, 
							 TrialTaskType a_type)
{
	assert( "trial" == node->type );

	TrialTask *task = new TrialTask(a_type);

	//
	// biopac
	//

	if ( doc->hasDescendant( doc->root(), "config biopac" ) ) {
		ParallelPortIO *ppio = new ParallelPortIO( task );

		ParallelPortIOConnector *connector_D0 
				= new ParallelPortIOConnector( ppio, ParallelPortIO::D0 );
		QObject::connect( task, SIGNAL( signal_start() ), connector_D0, SLOT( set_on() ) );
		QObject::connect( task, SIGNAL( signal_end() ), connector_D0, SLOT( set_off() ) );

		ParallelPortIOConnector *connector_D1 
				= new ParallelPortIOConnector( ppio, ParallelPortIO::D1 );
		QObject::connect( task, SIGNAL( signal_resume() ), connector_D1, SLOT( set_on() ) );
		QObject::connect( task, SIGNAL( signal_pause() ), connector_D1, SLOT( set_off() ) );
		QObject::connect( task, SIGNAL( signal_start() ), connector_D1, SLOT( set_on() ) );
		QObject::connect( task, SIGNAL( signal_end() ), connector_D1, SLOT( set_off() ) );
	}

	//
	// Parameters
	//
	const xml::DocumentNode* paramNode = doc->getReferencedNode(
			doc->getAttributeValue( node, "param" )
	);
	doc->process_children( paramNode, ParamNodeHandler( doc, task ) );

	//
	// Map
	//
	const xml::DocumentNode* mapNode = doc->getReferencedNode(
			doc->getAttributeValue( node, "map" )
	);
	doc->process_children( mapNode, MapNodeHandler( doc, task ) );

	//
	// Sky
	//
	const xml::DocumentNode* skyNode = doc->getReferencedNode(
			doc->getAttributeValue( node, "sky" )
	);
	doc->process_children( skyNode, SkyNodeHandler( doc, task ) );

	//
	// UI
	//
	if ( doc->attributeExists( node, "ui" ) ) {
		const xml::DocumentNode* uiNode = doc->getReferencedNode(
				doc->getAttributeValue( node, "ui" )
		);
		doc->process_children( uiNode, UINodeHandler( doc, task ) );
	}

	//
	// process children
	//
	doc->process_children( node, TrialNodeHandler( doc, task ) );

	task->initialize();

	return task;
}

////////////////////////////////////////////////////////////////////////////////////////////////////
